/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.app;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Debug;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.AndroidReceiverException;
import com.hercules.android.receiver.ApplicationConnectData;
import com.hercules.android.receiver.ApplicationData;
import com.hercules.android.receiver.DebugHelper;
import com.hercules.android.receiver.LocalBehaviorData;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.ReceiverUtil;
import com.hercules.android.receiver.factories.AndroidReceiverFactory;
import com.hercules.android.receiver.factories.AndroidReceiverFactoryException;
import com.hercules.android.receiver.handlers.events.listener.KeyboardEventListener;
import com.hercules.android.receiver.model.events.XREDeviceCapabilities;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.PaintersUtil;
import com.hercules.android.receiver.painting.XREObjectPainterException;
import com.hercules.android.receiver.painting.views.AndroidXREView;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * This activity is to be used by the application user to init the {@link AndroidReceiver} and then let the application
 * run (having the server take over).
 * <p>
 * This activity will be started by {@link SelectServerActivity}. It will load properties from
 * data/data/&lt;app_name&gt;/files/&lt;server_id&gt;.properties. After that it will create {@link AndroidReceiver}
 * using them and call connect method of receiver.
 * <p>
 * <b>Sample properties file:</b>
 *
 * <pre>
 * # Required properties
 * loggerName=logger
 * serverURL=url
 * applicationName=app_name
 *
 * # Required properties related to AndroidReceiverFactory
 * androidReceiverFactoryClassName=com.hercules.android.receiver.mocks.MockAndroidReceiverFactory
 * commandHandlerKey=commandHandler
 * serverConnectionKey=connection
 * errorHandlerKey=commandHandler
 *
 * # Optional properties
 * platform=Android
 * platformVersion=2.2
 * minimumVersion=2.1
 * receiverVersion=1.0
 * maxBadCommandAcceptCount=20
 * authenticationToken=token
 * onShutdownWaitInterval=3
 *
 * mimeTypes=media,video,picture
 *
 * appParam1=site\:\:topcoder
 * appParam2=reviewers\:\:are good
 * appParam3=year\:\:2010
 * </pre>
 *
 * <b>Required layout XML:</b>
 *
 * <pre>
 * &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 *   android:layout_width="fill_parent" android:layout_height="fill_parent"
 *   android:id="@+id/relative_layout"&gt;
 * &lt;/RelativeLayout&gt;
 * </pre>
 *
 * <b>Thread Safety:</b> This class is effectively thread safe.
 *
 * @author argolite, 7realm
 * @author 7realm
 * @version 1.0
 */
public class DisplayApplicationActivity extends Activity implements OnGestureListener {
    /** Represents server ID key in intent extras. */
    static final String SERVER_ID = "SERVER_ID";
    /** Represents system independent line separator. */
    private static final String EOL = System.getProperty("line.separator");
    /** Represents default value for platform property. */
    private static final String DEFAULT_PLATFORM = "Android";
    /** Represents default value for platform version property. */
    private static final String DEFAULT_PLATFORM_VERSION = "2.2";
    /** Represents default value receiver type property. */
    private static final String DEFAULT_RECEIVER_TYPE = "Android Receiver";
    /** Represents server connection key property name. */
    private static final String PROP_CONNECTION_KEY = "serverConnectionKey";
    /** Represents command handler key property name. */
    private static final String PROP_COMMAND_HANDLER_KEY = "commandHandlerKey";
    /** Represents error handler key property name. */
    private static final String PROP_ERROR_HANDLER_KEY = "errorHandlerKey";
    /** Represents platform property name. */
    private static final String PROP_PLATFORM = "platform";
    /** Represents platform version property name. */
    private static final String PROP_PLATFORM_VERSION = "platformVersion";
    /** Represents receiver type property name. */
    private static final String PROP_RECEIVER_TYPE = "receiverType";
    /** Represents receiver version property name. */
    private static final String PROP_RECEIVER_VERSION = "receiverVersion";
    /** Represents application name property name. */
    private static final String PROP_APPLICATION_NAME = "applicationName";
    /** Represents authentication token property name. */
    private static final String PROP_TOKEN = "authenticationToken";
    /** Represents minimum version property name. */
    private static final String PROP_MINIMUM_VERSION = "minimumVersion";
    /** Represents server url property name. */
    private static final String PROP_SERVER_URL = "serverURL";
    /** Represents logger name property name. */
    private static final String PROP_LOGGER_NAME = "loggerName";
    /** Represents maximum bad command count property name. */
    private static final String PROP_BAD_COMMAND_COUNT = "maxBadCommandAcceptCount";
    /** Represents shutdown wait interval property name. */
    private static final String PROP_WAIT_INTERVAL = "onShutdownWaitInterval";
    /** Represents prefix for application parameters property. */
    private static final String PROP_APP_PARAM_PREFIX = "appParam";
    /** Represents the local behavior data. */
    private final LocalBehaviorData localBehaviorData = new LocalBehaviorData();
    /** Represents the scanner */
    private GestureDetector gestureScanner = new GestureDetector(this);

    /**
     * Default empty constructor.
     */
    public DisplayApplicationActivity() {
        // empty
    }

    /**
     * Called when the activity is starting.
     *
     * @param savedInstanceState if the activity is being re-initialized after previously being shut down then this
     * Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle), otherwise it is null
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // AndroidAssembly1 changed root view class
        final AndroidXREView rootView = new AndroidXREView(this);
        rootView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        //rootView.setClipChildren(true);
        
        DebugHelper.currentActivity = this;
        
        Exception saved;
        try {
            // get passed server identifier
            String serverIdentifier = getIntent().getStringExtra(SERVER_ID);
            if (serverIdentifier == null) {
                throw new ConfigurationException("Intent string extra for ID '" + SERVER_ID + "' is not provided.");
            }

            DisplayMetrics displaymetrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
            
            final int contentWidth = Math.max(displaymetrics.widthPixels, 1280);
            final int contentHeight = Math.max(displaymetrics.heightPixels, 720);
            
            final AndroidXREView contentView = new AndroidXREView(this);
            setContentView(contentView, new FrameLayout.LayoutParams(contentWidth, contentHeight, Gravity.LEFT | Gravity.TOP));
            
            contentView.addView(rootView);
            
            // load properties
            Properties properties = loadProperties(serverIdentifier);
            
            DebugHelper.enableTrace = Boolean.valueOf(properties.getProperty("enableTrace", "false"));
            
            LogUtil.enableLog = Boolean.valueOf(properties.getProperty("enableLog", "false"));
            DebugHelper.testKeyboardEnabled = Boolean.valueOf(properties.getProperty("enableTestKeyboard", "false"));
            
            if (DebugHelper.testKeyboardEnabled) {
            	DebugHelper.keyboardView = (TableLayout) getLayoutInflater().inflate(R.layout.test_keyboard, null);
               
	            // attach click handlers for test keys
	            final Properties finalProperties = properties;
	            for (int i = 0; i < DebugHelper.keyboardView.getChildCount(); ++i) {
	            	TableRow row = (TableRow) DebugHelper.keyboardView.getChildAt(i);
	            	for (int k = 0; k < row.getChildCount(); ++k) {
	            		Button b = (Button) row.getChildAt(k);
	            		b.setOnClickListener(new View.OnClickListener() {
	                        public void onClick(View v) {
	                        	Button button = (Button) v;
	                        	int keyCode = Integer.parseInt(finalProperties.getProperty("keycode" + button.getText()));
	                        	
	                        	KeyEvent keyEvent;
	                        	if (keyCode == -1) {
	                        		keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_W, 0, KeyEvent.META_SHIFT_ON);
	                        	} else {
	                        		keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
	                        	}
	                        	
	                        	dispatchKeyEvent(keyEvent);
	                        }
	            		});
	            	}
	            }
	            
	            addContentView(DebugHelper.keyboardView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 
                		FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.RIGHT));
            }
          
            localBehaviorData.setRootView(rootView);
            localBehaviorData.setContentView(contentView);
            
            DebugHelper.usedImageMemorySize = 0;
            if (properties.containsKey("imageMemoryPoolSize")) {
            	DebugHelper.imageMemoryPoolSize = Integer.valueOf(properties.getProperty("imageMemoryPoolSize"));
            }
            
            // create receiver
            AndroidReceiver receiver = createReceiver(properties, rootView);

            // get application
            if (getApplication() instanceof ReceiverApplication) {
                ReceiverApplication application = (ReceiverApplication) getApplication();

                // set receiver in application
                application.setAndroidReceiver(receiver);
            } else {
                throw new ConfigurationException("Parent application is not ReceiverApplication.");
            }
            
            gestureScanner.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
            	
            	private boolean zoomedOut = true;

    			public boolean onDoubleTap(MotionEvent e) {
    				if (isRootViewHitted(e.getX(), e.getY())) {
    					final AndroidXREView rootView = localBehaviorData.getRootView();
    					final float appScaleRatio = localBehaviorData.getAppScaleRatio();
    					final float scrollX = rootView.getScrollX() * appScaleRatio;
    					final float scrollY = rootView.getScrollY() * appScaleRatio;
    					
    					final PointF center = new PointF(e.getX(), e.getY());
    					final PointF startScroll = new PointF(scrollX, scrollY);

    					if (zoomedOut) {
    						scaleApplication(center, startScroll, appScaleRatio, 2.f);
    					} else {
    						scaleApplication(center, startScroll, appScaleRatio, 0.5f);
    					}
    					
    					zoomedOut = !zoomedOut;
    				}
    				return false;
    			}

    			public boolean onDoubleTapEvent(MotionEvent e) {
    				return false;
    			}

    			public boolean onSingleTapConfirmed(MotionEvent e) {
    				return false;
    			} 
            	
            });
            
            // connect to server
            receiver.connect(false);
            
            if (DebugHelper.enableTrace) {
            	Debug.startMethodTracing("receiver_trace.trace");
            }
            
            return;
        } catch (AndroidReceiverException e) {
            saved = e;
        } catch (AndroidReceiverFactoryException e) {
            saved = e;
        } catch (IOException e) {
            saved = e;
        } catch (IllegalArgumentException e) {
            saved = e;
        } catch (ConfigurationException e) {
            saved = e;
        } catch (SecurityException e) {
            saved = e;
        } catch (XREObjectPainterException e) {
        	saved = e;
        }

        // process saved error
        TextView errorView = new TextView(this);
        errorView.append("Error occurred while initializing Activity." + EOL);
        errorView.append("Error class: " + saved.getClass() + EOL);
        errorView.append("Error details: " + saved.getMessage() + EOL);
        rootView.addView(errorView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    	super.onConfigurationChanged(newConfig);
    }
    /**
     * Clean up things.
     * <p>
     * AndroidAssembly1 new method
     */
    @Override
    protected void onDestroy() {
        if (getApplication() instanceof ReceiverApplication) {
            ReceiverApplication application = (ReceiverApplication) getApplication();

            PaintersUtil.cleanUp();
            
            try {
                application.getAndroidReceiver().shutdown(false);
            } catch (AndroidReceiverException e) {
                // log error
                LogUtil.error(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "onDestroy", e);
            }
        }

        super.onDestroy();
    }

    /**
     * Stop method profiling.
     */
    protected void onStop() {
    	super.onStop();
    	
    	if (DebugHelper.enableTrace) {
    		Debug.stopMethodTracing();
    	}
    }
    
    public boolean dispatchKeyEvent(KeyEvent event) {
    	
    	if (DebugHelper.testKeyboardEnabled) {
    		if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == DebugHelper.testKeyboardSwitchCode) {
	    		if (DebugHelper.keyboardView.getVisibility() == View.VISIBLE) {
	    			DebugHelper.keyboardView.setVisibility(View.GONE);
	    		} else {
	    			DebugHelper.keyboardView.setVisibility(View.VISIBLE);
	    		}
	    		localBehaviorData.getRootView().getRootView().postInvalidate();
    		}
    		
    		if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    			this.finish();
    		}
    	} else {
    		if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == DebugHelper.testKeyboardSwitchCode) {
    			InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
    			imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
    			//imm.s
	    		localBehaviorData.getRootView().getRootView().postInvalidate();
    		}
    	}
    	
    	final View focusedView = localBehaviorData.getFocusedView();
    	final AndroidReceiver focusedReceiver = localBehaviorData.getFocusedReceiver();
    	if (focusedView == null) { // || !focusedView.isShown()
    		return true;
    	}
    	
    	KeyboardEventListener listener =
            new KeyboardEventListener(focusedReceiver, localBehaviorData.getFocusedXREView(), "keyEvent", 
            		AndroidXREView.getEventRaiser(), AndroidXREView.getKeyMappingService());
    	
    	switch (event.getAction()) {
    	case KeyEvent.ACTION_DOWN:
    		focusedView.onKeyDown(event.getKeyCode(), event);
    		break;
    	case KeyEvent.ACTION_UP:
    		focusedView.onKeyUp(event.getKeyCode(), event);
    		break;
    	default:
    		break;
    	}
    	
    	listener.onKey(focusedView, event.getKeyCode(), event);
    	
    	return true;
    }
    
    /**
     * Dispatch touch event.
     */
   public boolean dispatchTouchEvent(MotionEvent event) {
	    final int actionIndex = event.getActionIndex();
    	final AndroidXREView rootView = localBehaviorData.getRootView();
    	final Map<Integer, PointF> points = localBehaviorData.getPoints();
    	final int[] scalePointIds = localBehaviorData.getScalePointIds();
    	final float appScaleRatio = localBehaviorData.getAppScaleRatio();
    	final AndroidReceiver receiver = ((ReceiverApplication) getApplication()).getAndroidReceiver();
        final int[] dimensions = receiver.getApplicationConnectData().getDeviceCaps().getNativeDimensions();
        final int receiverWidth = (int) (dimensions[0] * appScaleRatio);
		final int receiverHeight = (int) (dimensions[1] * appScaleRatio);
		final float scrollX = rootView.getScrollX() * appScaleRatio;
		final float scrollY = rootView.getScrollY() * appScaleRatio;
		final float contentScrollX = localBehaviorData.getContentView().getScrollX() * appScaleRatio;
		final float contentScrollY = localBehaviorData.getContentView().getScrollY() * appScaleRatio;
		
		LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
				"contentScrollX: {0}, contentScrollY: {1}, scrollX: {2}, scrollY: {3}", 
				contentScrollX, contentScrollY, scrollX, scrollY);

    	switch (event.getActionMasked()) {
    	case MotionEvent.ACTION_DOWN:
    	case MotionEvent.ACTION_POINTER_DOWN:
    		{
    			updatePointCoordinates(event);
    			LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
    					"action down - action index: {0}, count: {1}", actionIndex, event.getPointerCount());
    		}
    		break;
    	case MotionEvent.ACTION_POINTER_UP:
    		{
    			int pointerId = event.getPointerId(actionIndex);
    			points.remove(pointerId);
	    		localBehaviorData.setScaleStarted(false);
	    		LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
    					"action pointer up - action index: {0}, count: {1}", actionIndex, event.getPointerCount());
	    		localBehaviorData.setMoveStarted(false);
    		}
    		break;
    	case MotionEvent.ACTION_UP:
    	case MotionEvent.ACTION_CANCEL:
    		{
	    		points.clear();
	    		localBehaviorData.setScaleStarted(false);
	    		LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
    					"action up - action index: {0}", actionIndex);
	    		localBehaviorData.setMoveStarted(false);
    		}
    		break;
    	case MotionEvent.ACTION_MOVE:
    		{
	    		if (points.size() == 1) {
	    			DisplayMetrics displaymetrics = new DisplayMetrics();
	                getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
	
	                final int deviceWidth = displaymetrics.widthPixels;
	                final int deviceHeight = displaymetrics.heightPixels;
	        		
	        		int leftBound = deviceWidth < receiverWidth ?  (receiverWidth - deviceWidth) : 0;
	        		int topBound = deviceHeight < receiverHeight ? (receiverHeight - deviceHeight) : 0;
	        		int rightBound = deviceWidth > receiverWidth ?  (receiverWidth - deviceWidth) : 0;
	        		int bottomBound = deviceHeight > receiverHeight ? (receiverHeight - deviceHeight) : 0;

	        		if (localBehaviorData.getMoveStarted()) {
	        			int pointerId = points.keySet().iterator().next();
	        			PointF lastPoint = points.get(pointerId);
	        			
		        		float offsetX = lastPoint.x - event.getX();
		        		float offsetY = lastPoint.y - event.getY();
		        		
		        		float newX = scrollX + offsetX;
		        		float newY = scrollY + offsetY;
		        		
		        		newX = offsetX > 0 ? (newX > leftBound ? scrollX : newX) : (newX < rightBound ? scrollX : newX);
		        		newY = offsetY > 0 ? (newY > topBound ? scrollY : newY) : (newY < bottomBound ? scrollY : newY);
		        		
		        		rootView.getRootView().postInvalidate();
		        		
		        		rootView.scrollTo((int) (newX / appScaleRatio), (int) (newY / appScaleRatio));

		        		points.put(pointerId, new PointF(event.getX(), event.getY()));
		        		
		        		LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
		        				"lastPoint: {0}, {1}, newPoint: {2}, {3}, newX: {4}, newY: {5}, scrollX: {6}, scrollY: {7}", 
		        				lastPoint.x, lastPoint.y, event.getX(), event.getY(), newX, newY, scrollX, scrollY);
	        		}
	        		
	        		localBehaviorData.setMoveStarted(true);
	    		} else {
	    			if (!localBehaviorData.getScaleStarted()) {
	    				int count = 0;
	    				PointF[] startPoints = new PointF[2];
	    				for (int i = 0; i < event.getPointerCount() && count <= 2; ++i) {
	    					if (count > 0) {
			    				float centerX = (startPoints[0].x + event.getX(i)) / 2;
			    				float centerY = (startPoints[0].y + event.getY(i)) / 2;
			    				
			    				if (!isRootViewHitted(centerX, centerY)) {
			    					continue;
			    				}
    						}
	    					
    						localBehaviorData.getScalePointIds()[count] = event.getPointerId(i);
    						startPoints[count] = new PointF(event.getX(i), event.getY(i));
    						++ count;
	    				}
	    				
	    				if (count >= 2) {
		    				PointF v = new PointF(startPoints[0].x - startPoints[1].x, startPoints[0].y - startPoints[1].y);
		    				localBehaviorData.setScaleStartDistance(v.length());
		    				
		    				PointF center = new PointF();
		    				center.x = (startPoints[0].x + startPoints[1].x) / 2;
		    				center.y = (startPoints[0].y + startPoints[1].y) / 2;
		    				localBehaviorData.setScaleStartCenter(center);
	
		    				localBehaviorData.setScaleStartValue(appScaleRatio);
		    				localBehaviorData.setScaleStartScroll(new PointF(scrollX, scrollY));
		    				localBehaviorData.setScaleStarted(true);
	    				}
	    			} else {
	    				float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	    				for (int i = 0; i < event.getPointerCount(); ++i) {
	    					int pointerId = event.getPointerId(i);
	    					if (pointerId == scalePointIds[0]) {
	    						x1 = event.getX(i) + scrollX;
	    						y1 = event.getY(i) + scrollY;
	    					} else if (pointerId == scalePointIds[1]) {
	    						x2 = event.getX(i) + scrollX;
	    						y2 = event.getY(i) + scrollY;
	    					}
	    				}
	    				
	    				float newDistance = (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
	    				float scale = newDistance / localBehaviorData.getScaleStartDistance();
	    				
	    				PointF center = localBehaviorData.getScaleStartCenter();
	    				PointF startScroll = localBehaviorData.getScaleStartScroll();

	    				float newAppScale = scale * localBehaviorData.getScaleStartValue();
	    				newAppScale = newAppScale > 1.5f ? 1.5f : (newAppScale < 0.1f ? 0.1f : newAppScale);
	    				
	    				float clampedScale = newAppScale / localBehaviorData.getScaleStartValue();
	    				float newScrollX = (center.x + startScroll.x) * clampedScale - center.x;
	    				float newScrollY = (center.y + startScroll.y) * clampedScale - center.y;
	    				
	    				applyAppScaleRatio(newAppScale);
	    				
	    				rootView.getRootView().postInvalidate();
	    				
	    				rootView.scrollTo((int) (newScrollX / newAppScale), (int) (newScrollY / newAppScale));
	    				
	    				LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
	    						"newAppScale: {0}, newScrollX: {1}, newScrollY: {2}", newAppScale, newScrollX, newScrollY);
	    			}
	    			
	    			localBehaviorData.setMoveStarted(false);
	    		}
	    		
	    		updatePointCoordinates(event);
	    		
	    		LogUtil.info(Logger.getLogger(DisplayApplicationActivity.class.getSimpleName()), this, "dispatchTouchEvent", 
    					"action move");
    		}
    		break;
    	default:
    		break;
    	}
    	
    	gestureScanner.onTouchEvent(event);
    	
    	return super.dispatchTouchEvent(event);
    }
    
   private boolean isRootViewHitted(float x, float y) {
	   final AndroidXREView rootView = localBehaviorData.getRootView();
	   final float appScaleRatio = localBehaviorData.getAppScaleRatio();
	   final float scrollX = rootView.getScrollX() * appScaleRatio;
	   final float scrollY = rootView.getScrollY() * appScaleRatio;
	   final AndroidReceiver receiver = ((ReceiverApplication) getApplication()).getAndroidReceiver();
	   final int[] dimensions = receiver.getApplicationConnectData().getDeviceCaps().getNativeDimensions();
       final int receiverWidth = (int) (dimensions[0] * appScaleRatio);
       final int receiverHeight = (int) (dimensions[1] * appScaleRatio);
       
	   RectF hitRect = new RectF();
	   hitRect.left = - scrollX;
	   hitRect.top = - scrollY;
	   hitRect.right = - scrollX + receiverWidth;
	   hitRect.bottom = - scrollY + receiverHeight;
	   
	   return hitRect.contains(x, y);
   }
   
   private void scaleApplication(PointF center, PointF startScroll, float scaleStartValue, float scale) {
	   final AndroidXREView rootView = localBehaviorData.getRootView();
	   
	   float newAppScale = scale * scaleStartValue;
	   newAppScale = newAppScale > 1.5f ? 1.5f : (newAppScale < 0.1f ? 0.1f : newAppScale);

	   final float clampedScale = newAppScale / scaleStartValue;
	   final float newScrollX = (center.x + startScroll.x) * clampedScale - center.x;
	   final float newScrollY = (center.y + startScroll.y) * clampedScale - center.y;
		
	   applyAppScaleRatio(newAppScale);
	   rootView.scrollTo((int) (newScrollX / newAppScale), (int) (newScrollY / newAppScale));
   }
   
   public boolean onDown(MotionEvent e) {
		return false;
	}

	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		return false;
	}

	public void onLongPress(MotionEvent e) {
		// do nothing
	}

	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
		return false;
	}

	public void onShowPress(MotionEvent e) {
		// do nothing
	}

	public boolean onSingleTapUp(MotionEvent e) {
		return false;
	}
	
    /**
     * Creates {@link AndroidReceiver} from properties.
     *
     * @param rootView the root view
     * @param properties the configuration properties
     * @return the created instance of {@link AndroidReceiver}
     * @throws ConfigurationException if any configuration error occurred
     * @throws AndroidReceiverFactoryException if failed to create objects by key, or keys are not recognizable
     * @throws XREObjectPainterException if any error occurs while attaching the XRE view
     */
    private AndroidReceiver createReceiver(Properties properties, AndroidXREView rootView) throws ConfigurationException,
        AndroidReceiverFactoryException, XREObjectPainterException {
        String serverConnectionKey = getRequired(properties, PROP_CONNECTION_KEY);
        String commandHandlerKey = getRequired(properties, PROP_COMMAND_HANDLER_KEY);
        String errorHandlerKey = getRequired(properties, PROP_ERROR_HANDLER_KEY);

        String maxBadCountStr = properties.getProperty(PROP_BAD_COMMAND_COUNT);
        String waitItervalStr = properties.getProperty(PROP_WAIT_INTERVAL);
        
        // create factory
        AndroidReceiverFactory factory = createImpl(properties);

        XREDeviceCapabilities deviceCaps = new XREDeviceCapabilities();
        deviceCaps.setPlatform(properties.getProperty(PROP_PLATFORM, DEFAULT_PLATFORM));
        deviceCaps.setPlatformVersion(properties.getProperty(PROP_PLATFORM_VERSION, DEFAULT_PLATFORM_VERSION));
        // AndroidAssembly1 added receiver type
        deviceCaps.setReceiverType(properties.getProperty(PROP_RECEIVER_TYPE, DEFAULT_RECEIVER_TYPE));
        deviceCaps.setReceiverVersion(properties.getProperty(PROP_RECEIVER_VERSION));
        deviceCaps.setMimeTypes(getMimeTypes(properties));

        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        
        final int[] nativeDimension = {1280, 720};
        deviceCaps.setNativeDimensions(nativeDimension);

        ApplicationConnectData appConnectData = new ApplicationConnectData();
        appConnectData.setApplicationName(getRequired(properties, PROP_APPLICATION_NAME));
        appConnectData.setAppParams(getAppParams(properties));
        appConnectData.setAuthenticationToken(properties.getProperty(PROP_TOKEN));
        appConnectData.setMinimumVersion(properties.getProperty(PROP_MINIMUM_VERSION));
        appConnectData.setUrl(getRequired(properties, PROP_SERVER_URL));
        appConnectData.setDeviceCaps(deviceCaps);

        // create new receiver
        AndroidReceiver receiver = new AndroidReceiver();
        receiver.setApplicationConnectData(appConnectData);
        // AndroidAssembly1 application data should be set in connect command
        receiver.setLogger(LogUtil.getLogger(properties.getProperty(PROP_LOGGER_NAME)));
        receiver.setRootView(rootView);
        receiver.setErrorHandler(factory.createErrorHandler(errorHandlerKey));
        receiver.setServerConnection(factory.createServerConnection(serverConnectionKey));
        receiver.setCommandHandler(factory.createXREAggregateCommandHandler(commandHandlerKey));
        receiver.setLocalBehaviorData(localBehaviorData);

        // set integer properties
        if (maxBadCountStr != null) {
            receiver.setMaxBadCommandAcceptCount(parseInt(maxBadCountStr, PROP_BAD_COMMAND_COUNT));
        }
        if (waitItervalStr != null) {
            receiver.setOnShutdownWaitInterval(parseInt(waitItervalStr, PROP_WAIT_INTERVAL));
        }
        
        ApplicationData applicationData = new ApplicationData();
        receiver.setApplicationData(applicationData);
        
        Map<Integer, XREObject> elementsMap = new HashMap<Integer, XREObject>();
        applicationData.setElementsXREIdToObject(elementsMap);
        
    	XREView rootXREView = new XREView();
        rootXREView.setId(XREObject.ID_ROOT_VIEW);
        rootXREView.setDimensions(nativeDimension);
        // AndroidAssembly1 do not set application for root view
        elementsMap.put(rootXREView.getId(), rootXREView);

        // attach root UI view
        rootView.setReceiver(receiver);
		rootView.attachView(rootXREView);
		rootView.updateLayout();
		
        // scale root view
        // get screen dimensions
        Display display = getWindowManager().getDefaultDisplay();
        int deviceHeight = display.getHeight();
        int deviceWidth = display.getWidth();
        
        float scaleX = deviceWidth < nativeDimension[0] ? ((float) deviceWidth / nativeDimension[0]) : 1.0f;
        float scaleY = deviceHeight < nativeDimension[1] ? ((float) deviceHeight / nativeDimension[1]) : 1.0f;
        
        float appScaleRatio = scaleX < scaleY ? scaleX : scaleY;
        applyAppScaleRatio(appScaleRatio);
        
        return receiver;
    }
    
    private void applyAppScaleRatio(float appScaleRatio) {
    	AndroidXREView rootView = localBehaviorData.getRootView();
    	double[][] matrix = rootView.getXREView().getMatrix();
		matrix[0][0] = matrix[0][0] / localBehaviorData.getAppScaleRatio() * appScaleRatio;
		matrix[1][1] = matrix[1][1] / localBehaviorData.getAppScaleRatio() * appScaleRatio;
		
		rootView.getXREView().getAndroidXREView().updateActualMatrix();
		
		localBehaviorData.setAppScaleRatio(appScaleRatio);
		rootView.postInvalidate();
    }
    
    private void updatePointCoordinates(MotionEvent event) {
    	final Map<Integer, PointF> points = localBehaviorData.getPoints();
    	
    //	points.clear();
		for (int i = 0; i < event.getPointerCount(); ++i) {
			int pointerId = event.getPointerId(i);
			points.put(pointerId, new PointF(event.getX(i), event.getY(i)));
		}
    }
    
    /**
     * Gets application parameters property from properties.
     *
     * @param properties the configuration properties
     * @return the property value
     * @throws ConfigurationException if property value is incorrect
     */
    private static Map<String, Object> getAppParams(Properties properties) throws ConfigurationException {
        Map<String, Object> result = new HashMap<String, Object>();

        // search all properties
        int number = 1;
        while (true) {
            String name = PROP_APP_PARAM_PREFIX + number++;
            String value = properties.getProperty(name);
            if (value == null) {
                break;
            }

            // split value
            String[] parts = value.split("\\:\\:", 2);
            if (parts.length != 2) {
                throw new ConfigurationException("Value for property '" + name
                    + "' must have '::' name-value delimiter.");
            }

            result.put(parts[0], parts[1]);
        }

        return result;
    }

    /**
     * Gets mime types value from properties.
     *
     * @param properties the configuration properties
     * @return list of mime types or empty list
     */
    private static List<String> getMimeTypes(Properties properties) {
        String propertyValue = properties.getProperty("mimeTypes");

        // AndroidAssembly1 changed to empty list return
        // return null value or comma splitted list
        return propertyValue == null ? new ArrayList<String>() : Arrays.asList(propertyValue.split(","));
    }

    /**
     * Gets required property value.
     *
     * @param properties the configuration properties
     * @param name the name of the property to get
     * @return the property value
     * @throws ConfigurationException if property is missing or empty
     */
    private static String getRequired(Properties properties, String name) throws ConfigurationException {
        String value = properties.getProperty(name);
        if (value == null) {
            throw new ConfigurationException("Required property '" + name + "' is not present.");
        }
        if (value.trim().length() == 0) {
            throw new ConfigurationException("Required property '" + name + "' has trimmed empty value.");
        }
        return value;
    }

    /**
     * Parse integer property.
     *
     * @param value the property value
     * @param name the name of the property
     * @return the parsed value
     * @throws ConfigurationException if property value is cannot be parsed
     */
    private static int parseInt(String value, String name) throws ConfigurationException {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new ConfigurationException("Failed to parse integer property '" + name + "'.", e);
        }
    }

    /**
     * Creates implementation if {@link AndroidReceiverFactory} interface.
     *
     * @param properties the configuration properties
     * @return the created implementation of given interface
     * @throws ConfigurationException if any error occurred during creation of implementation
     */
    private AndroidReceiverFactory createImpl(Properties properties) throws ConfigurationException {
        String className = getRequired(properties, "androidReceiverFactoryClassName");

        try {
            // AndroidAssembly1 introduced Context constructor
            // get class for given name
            Class<?> clazz = Class.forName(className);

            // get parameter-less constructor
            Constructor<?> ctor = clazz.getConstructor(Context.class);

            // create instance and check if it is correct
            // Please note, that IllegalArgumentException will never be thrown here
            Object instance = ctor.newInstance(this);
            if (!(instance instanceof AndroidReceiverFactory)) {
                throw new ConfigurationException("Class '" + className + "' do not implement '"
                    + AndroidReceiverFactory.class + "' interface.");
            }
            return (AndroidReceiverFactory) instance;
        } catch (ClassNotFoundException e) {
            throw new ConfigurationException("The '" + className + "' class is not found.", e);
        } catch (NoSuchMethodException e) {
            throw new ConfigurationException("The '" + className + "' do not have required constructor.", e);
        } catch (SecurityException e) {
            throw new ConfigurationException("Security error occurs while getting "
                + "required constructor of the class '" + className + "'.", e);
        } catch (InstantiationException e) {
            throw new ConfigurationException("The '" + className + "' is an abstract class.", e);
        } catch (IllegalAccessException e) {
            throw new ConfigurationException("The required constructor of '" + className + "' is inaccessible.", e);
        } catch (InvocationTargetException e) {
            throw new ConfigurationException("An exception was thrown by the invoked constructor of the class '"
                + className + "'.", e);
        } catch (ExceptionInInitializerError e) {
            throw new ConfigurationException("Failed to initialize class '" + className + "'.", e);
        } catch (LinkageError e) {
            throw new ConfigurationException("Linkage failed for class '" + className + "'.", e);
        }
    }

    /**
     * Loads properties from server ID.
     *
     * @param serverId the current server identifier
     * @return loaded properties
     * @throws IOException if I/O error occurs
     * @throws ConfigurationException if reflection related error occurs
     */
    private Properties loadProperties(String serverId) throws IOException, ConfigurationException {
        try {
            Field field = R.raw.class.getField(serverId.toLowerCase());
            return ReceiverUtil.loadProperties(this, field.getInt(null));
        } catch (NoSuchFieldException e) {
            throw new ConfigurationException("No configured resource for server ID '" + serverId + "'.", e);
        } catch (NotFoundException e) {
            throw new ConfigurationException("No configured resource for server ID '" + serverId + "'.", e);
        } catch (IllegalArgumentException e) {
            throw new ConfigurationException("Incorrect resource ID R.raw." + serverId + ".", e);
        } catch (IllegalAccessException e) {
            throw new ConfigurationException("Resource ID is not accessible R.raw." + serverId + ".", e);
        }
    }

    /**
     * Internal exception, used for configuration errors.
     *
     * @author 7realm
     * @version 1.0
     */
    public static class ConfigurationException extends Exception {
        /** Serial version UID. */
        private static final long serialVersionUID = -1050120701730567807L;

        /**
         * Creates a new instance of this exception with error message.
         *
         * @param message the detailed error message of this exception
         */
        public ConfigurationException(String message) {
            super(message);
        }

        /**
         * Creates a new instance of this exception with error message and inner cause.
         *
         * @param message the detailed error message of this exception
         * @param cause the inner cause of this exception
         */
        public ConfigurationException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}